package Parser;

import org.antlr.v4.runtime.tree.TerminalNode;
import Error.Log;
import org.apache.commons.lang3.StringUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.TreeMap;
import java.util.Stack;

public class Symbol_Tree extends WezlangBaseListener {
	
	//Handles inserting named variables into the symboltable
	static class SymbolTable {
		TreeMap<String, Type> sym_table = new TreeMap<String, Type>();
		ArrayList<SymbolTable> child_Tables = new ArrayList<SymbolTable>();
		
		void addSymbol(String name, Type type){
			if (sym_table.containsKey(name)) 
				sym_table.get(name).merge(type);
			else 
				sym_table.put(name, type);
		}
		
		void addChildTable(SymbolTable child){
			child_Tables.add(child);
		}
		
		public String toString(int depth){
			String indent = StringUtils.repeat(" ", depth);
			String out = indent + "Sym Tables \n";
			for(String name: sym_table.descendingKeySet()){
				out += " " + indent + "name= " + name + " type= " + sym_table.get(name) + "\n";
			}
			out +=	indent + "Child Tables: \n";
			for(SymbolTable table: child_Tables){
				out += " "  + indent + table.toString(depth + 1) + "\n";
			}
			return out;
		}
	}
	
	static class Type {
		final boolean type_declared;
		final boolean arg_type;
		WezlangParser.TypeContext type;
		private WezlangParser.ExprContext expr;
		
		Type(){
			type_declared = false;
			arg_type = true;
		}
		
		Type(WezlangParser.ExprContext expr){
			type_declared = false;
			type = null;
			this.expr = expr;
			arg_type = false;
		}
		Type(WezlangParser.TypeContext type){
			type_declared = true;
			this.type = type;
			arg_type = false;
		}
		
		void merge(Type type){
			if (type_declared && type.type_declared)
				Log.print("Variable declared twice");
			else if (type.type_declared)
				Log.print("Variable type signature comes after variable");
			else if (!type.type_declared && !type_declared)
				Log.print("Variable is redefined");
			else {
				this.expr = type.expr;
			}
		}
		
		public String toString(){
			if (type_declared)
				return "Type decl=" + type_declared + "; Type= " + type.getText() + "; " + expr.getText();
			else if (expr == null)
				return "Type decl=" + type_declared + "; Type= \"\" ; ";
			else 
				return "Type decl=" + type_declared + "; Type= \"\" ; " + expr.getText();
		}
	}		
	
	Stack<SymbolTable> sym_stack = new Stack<SymbolTable>();
	SymbolTable top;
	public Symbol_Tree(){
		sym_stack.push(new SymbolTable());
		top = sym_stack.peek();
	}

	
	public static void proccessLines(Stack<SymbolTable> sym_stack, List<WezlangParser.LineContext> lines){
		//A utility function that adds all the symbol variables to the symbol tables.
		for (WezlangParser.LineContext line: lines) {
			if (line.typed_var() != null) {
				Type symbol_type = new Type(line.typed_var().type());
				String symbol_var = line.typed_var().NAME().getText();
				sym_stack.peek().addSymbol(symbol_var, symbol_type);
			} else if (line.variable() != null) {	
				sym_stack.peek().addSymbol(line.variable().NAME().getText(), new Type(line.variable().expr()));
			}
		}
	}
	
	static void push_new(Stack<SymbolTable> sym_stack){
		SymbolTable symTable = new SymbolTable();
		sym_stack.peek().addChildTable(symTable);
		sym_stack.push(symTable);
	}
	
	public void enterBlock(WezlangParser.BlockContext ctx) { 
		
		//Make a new symbol table and insert it into the 
		//the above symbol table
		push_new(sym_stack);
		
		//Go through the program lines and fill the symbol table with variable
		//declarations and 
		proccessLines(sym_stack, ctx.line());
	}
	
	public void exitBlock(WezlangParser.BlockContext ctx) {
		sym_stack.pop();
	}
	
	public void enterFunction(WezlangParser.FunctionContext ctx){	
		push_new(sym_stack);
		for(TerminalNode argument: ctx.arg_list().NAME()){
			sym_stack.peek().addSymbol(argument.getText(), new Type());
		}
		proccessLines(sym_stack, ctx.line());
	}
	
	public void exitFunction(WezlangParser.FunctionContext ctx){
		sym_stack.pop();
	}
	
	public void enterProg(WezlangParser.ProgContext ctx) {
		proccessLines(sym_stack, ctx.line());
	}
	
	public String toString(){
		return top.toString(0);
		
	}
}
